package com.zeny.express.framework.core.mybatis.interceptor;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.MappedStatement.Builder;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import com.zeny.express.framework.core.domain.Page;
import com.zeny.express.framework.core.mybatis.dialect.Dialect;
import com.zeny.express.framework.core.service.PaginationService;
import com.zeny.express.framework.core.support.SpringContext;
import com.zeny.express.framework.shared.serializer.JsonSerializer;

/**
 * 为mybatis提供基于方言(Dialect)的分页查询的插件
 * 
 * 将拦截Executor.query()方法实现分页方言的插入.
 * 
 * 
 * @author zhuwei
 * @date 2013年8月18日 下午5:20:05
 */
@Intercepts({ @Signature(type = Executor.class, method = "query", args = {
		MappedStatement.class, Object.class, RowBounds.class,
		ResultHandler.class }) })
public class PaginationInterceptor implements Interceptor {
	private static int mappedStatementIndex = 0;
	private static int parameterIndex = 1;
	private static int rowboundsIndex = 2;
	// private static int resultHandlerIndex = 3;
	
	@SuppressWarnings("rawtypes")
	private static JsonSerializer<Map> serializer = new JsonSerializer<Map>(false, Map.class);
	
	/**
	 * 方言
	 */
	private Dialect dialect;

	/**
	 * 拦截分页请求，将原sql转化成分页sql
	 * 
	 * @author zhuwei
	 * @date 2013年8月18日 下午6:00:16
	 * @see org.apache.ibatis.plugin.Interceptor#intercept(org.apache.ibatis.plugin.Invocation)
	 */
	public Object intercept(Invocation invocation) throws Throwable {
		// queryArgs = query(MappedStatement ms, Object parameter, RowBounds
		// rowBounds, ResultHandler resultHandler)

		Object[] queryArgs = invocation.getArgs();
		
		// 如果参数是rowbounds，则不做处理
		if (!(queryArgs[rowboundsIndex] instanceof Page<?>)) {
			return invocation.proceed();
		}

		MappedStatement ms = (MappedStatement) queryArgs[mappedStatementIndex];
		Object parameter = queryArgs[parameterIndex];
		Page<?> page = (Page<?>) queryArgs[rowboundsIndex];
		BoundSql boundSql = ms.getBoundSql(parameter);

		// 汇总查询
		if (page.getTotalRows() == 0 && page.isTotalCounted()) {

			PaginationService bean = SpringContext.getBean(PaginationService.class);
			Map<String, Object> result = null;
			
			if (page.isTotalCached()) {
				
				Map<String, Object> map = new HashMap<String, Object>();
				map.put("id", ms.getId());
				map.put("parameter", parameter);
				
				String key = new String(serializer.serialize(map));

				result = bean.getCount(key, ms, boundSql, page);
			} else {

				result = bean.getCount(ms, boundSql, page);
			}
			
			int totalRows = Integer.valueOf(result.get(Page.COUNT_ALIAS).toString());
			page.calculate(totalRows);
			page.setAggregationResult(result);

		}

		// 翻页查询
		if (dialect.supportsLimit()) {
			int limit = page.getLimit();
			int offset = page.getOffset();

			String sql = boundSql.getSql().trim();
			if (dialect.supportsLimit()) {
				sql = dialect.getLimitString(sql, offset, limit,
						page.getOrderMap());
				offset = RowBounds.NO_ROW_OFFSET;
			} else {
				sql = dialect.getLimitString(sql, 0, limit, page.getOrderMap());
			}
			limit = RowBounds.NO_ROW_LIMIT;

			queryArgs[rowboundsIndex] = new RowBounds(offset, limit);
			BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql,
					boundSql.getParameterMappings(),
					boundSql.getParameterObject());
			// 将原有的BoundSql中的MetaParameter复制到新的BoundSql中
			copyMetaParameters(boundSql, newBoundSql);
			MappedStatement newMs = copyFromMappedStatement(ms,
					new BoundSqlSqlSource(newBoundSql));
			queryArgs[mappedStatementIndex] = newMs;
		}

		return invocation.proceed();
	}

	/**
	 * 复制BoundSql的MetaParameter
	 * 
	 * @param lhs
	 * @param rhs
	 */
	private void copyMetaParameters(BoundSql lhs, BoundSql rhs) {
		for (ParameterMapping map : lhs.getParameterMappings()) {
			String key = map.getProperty();
			Object value = lhs.getAdditionalParameter(key);
			if (null != value) {
				rhs.setAdditionalParameter(key, value);
			}
		}
	}

	/**
	 * 获取MappedStatement
	 * 
	 * @author zhuwei
	 * @date 2013年8月18日 下午5:26:43
	 * @param ms
	 * @param newSqlSource
	 * @return
	 */
	private MappedStatement copyFromMappedStatement(MappedStatement ms,
			SqlSource newSqlSource) {
		Builder builder = new MappedStatement.Builder(ms.getConfiguration(),
				ms.getId(), newSqlSource, ms.getSqlCommandType());

		builder.resource(ms.getResource());
		builder.fetchSize(ms.getFetchSize());
		builder.statementType(ms.getStatementType());
		builder.keyGenerator(ms.getKeyGenerator());
		if (ms.getKeyProperties() != null) {
			for (String keyProperty : ms.getKeyProperties()) {
				builder.keyProperty(keyProperty);
			}
		}

		builder.timeout(ms.getTimeout());
		builder.parameterMap(ms.getParameterMap());
		builder.resultMaps(ms.getResultMaps());
		builder.resultSetType(ms.getResultSetType());

		builder.cache(ms.getCache());
		builder.useCache(ms.isUseCache());
		builder.flushCacheRequired(ms.isFlushCacheRequired());

		return builder.build();
	}

	/**
	 * plugin
	 * 
	 * @author zhuwei
	 * @date 2013年8月18日 下午5:36:34
	 * @see org.apache.ibatis.plugin.Interceptor#plugin(java.lang.Object)
	 */
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	/**
	 * 设置方言
	 * 
	 * @author zhuwei
	 * @date 2013年8月18日 下午5:27:08
	 * @see org.apache.ibatis.plugin.Interceptor#setProperties(java.util.Properties)
	 */
	public void setProperties(Properties properties) {
		String dialectClass = properties.getProperty("dialectClass");
		try {
			dialect = (Dialect) Class.forName(dialectClass).newInstance();
		} catch (Exception e) {
			throw new RuntimeException(
					"cannot create dialect instance by dialectClass:"
							+ dialectClass, e);
		}
	}

	/**
	 * 设置分页boundsql
	 * 
	 * @author zhuwei
	 * @date 2013年8月18日 下午5:27:16
	 */
	public static class BoundSqlSqlSource implements SqlSource {
		private BoundSql boundSql;

		public BoundSqlSqlSource(BoundSql boundSql) {
			this.boundSql = boundSql;
		}

		public BoundSql getBoundSql(Object parameterObject) {
			return boundSql;
		}
	}

}
